import base64
import hashlib
import json
import random
import re
import time
from Crypto.Cipher import AES
from typing import Dict, List, Optional


class BaseH5st:
    def __init__(self, h5st_version, local_token_version, h5st_algo_config, custom_algorithm, local_token):
        self.h5st_version = h5st_version
        self.local_token_version = local_token_version
        self.h5st_algo_config = h5st_algo_config
        self.algos = custom_algorithm
        self.local_token = local_token

        self.default_algorithm = {
            'local_key_1': lambda message: self.algos.md5(message),
            'local_key_2': lambda message: self.algos.sha256(message),
            'local_key_3': lambda message, key: self.algos.hmac_sha256(message, key)
        }
        self._debug = False
        self._appId = ""
        self._fingerprint = ""
        self._default_token = ""

    def _log(self, message: str) -> None:
        if self._debug:
            print(message)

    def __gen_default_key(self, token: str, fingerprint: str, time_str: str, app_id: str) -> str:
        input_str = f"{token}{fingerprint}{time_str}{app_id}{self.h5st_algo_config['defaultKey']['extend']}"
        express = self.decode_base64_url(self.__parse_token(token, 16, 28))
        key = ""
        # Example pattern matching
        express_match = re.match(r"^[123]([x+][123])+", express)
        if express_match:
            express_hit = express_match.group(0).split()
            key_router = ""
            for router in express_hit:
                if router in ['+', 'x']:
                    key_router = router
                else:
                    algo_key = f"local_key_{router}"
                    if algo_key in self.default_algorithm:
                        if key_router == '+':
                            key += self.__algorithm(algo_key, input_str, token)
                        elif key_router == 'x':
                            key = self.__algorithm(algo_key, key, token)
                        else:
                            key = self.__algorithm(algo_key, input_str, token)
        self._log(f"__gen_default_key input={input_str}, key={key}")
        return key

    def __algorithm(self, algo_key: str, input_str: str, token: str) -> str:
        if algo_key == "local_key_3":
            return self.default_algorithm[algo_key](input_str, token).hexdigest()
        elif algo_key in ["local_key_1", "local_key_2"]:
            return self.default_algorithm[algo_key](input_str).hexdigest()
        else:
            raise ValueError("Unsupported algorithm key")

    def __parse_token(self, token: str, begin: int, end: int) -> str:
        return token[begin:end]

    def __gen_sign_params(self, body_sign: str, timestamp: int, time_str: str, env_sign: str,
                          sign_str_default: Optional[str] = None) -> str:
        sign_str_default = f";{sign_str_default}" if sign_str_default else ""
        return f"{time_str};{self._fingerprint};{self._appId};{self._default_token};{body_sign};{self.h5st_algo_config['version']};{timestamp};{env_sign}{sign_str_default}"

    def sign(self, params: Dict[str, str]) -> Dict[str, str]:
        try:
            timestamp = int(time.time() * 1000)
            date_str = time.strftime('%Y%m%d%H%M%S%f', time.localtime())[:-3]

            filter_params = {key: hashlib.sha256(params[key].encode()).hexdigest() if key == "body" else params[key] for
                             key in ["functionId", "appid", "client", "body", "clientVersion", "sign", "t", "jsonp"] if
                             key in params}
            check_params = self.__check_params(filter_params)
            if check_params is None:
                return filter_params

            self.__request_deps()
            env_sign = self.__collect()
            make_sign = self.__make_sign(check_params, env_sign)
            self._log(f"sign elapsed time: {int(time.time() * 1000) - timestamp}ms")
            return {**filter_params, **make_sign}
        except Exception as e:
            self._log(f"unknown error: {str(e)}")
            return params

    def __check_params(self, body: Dict[str, str]) -> Optional[List[Dict[str, str]]]:
        if not self._appId:
            self._log("App ID is required.")
            return None
        if not isinstance(body, dict):
            self._log("Parameters must be a dictionary.")
            return None
        if not body:
            self._log("Parameters are empty.")
            return None
        return [{"key": k, "value": v} for k, v in body.items()]

    def __request_deps(self) -> None:
        self._log("__request_deps start")
        if not self._fingerprint:
            self._fingerprint = self.generate_visit_key()
            self._log(f"Generated new fingerprint: {self._fingerprint}")

    def __collect(self) -> str:
        env = self.env_collect()
        env["fp"] = self._fingerprint
        return self.env_sign(json.dumps(env, indent=2))

    def env_collect(self) -> Dict[str, str]:
        canvas = self.algos.md5(str(random.randint(0, 100))).hexdigest()
        webgl_fp = self.algos.md5(str(random.randint(0, 100))).hexdigest()
        return {
            "pp": {"p1": self.pt_pin} if self.pt_pin else {},
            "random": self.get_random_id_pro(),
            "sua": re.search(r'Mozilla/5.0 \((.*?)\)', self.user_agent).group(1) if re.search(r'Mozilla/5.0 \((.*?)\)',
                                                                                              self.user_agent) else "",
            "canvas": canvas,
            "canvas1": canvas,
            "webgl_fp": webgl_fp,
            "webgl_fp1": webgl_fp
        }

    def env_sign(self, message: str) -> str:
        cipher = AES.new(self.h5st_algo_config['env']['secret'].encode(), AES.MODE_CBC, iv="0102030405060708".encode())
        encrypted = cipher.encrypt(message.encode().ljust((len(message) + 15) // 16 * 16))
        return base64.b64encode(encrypted).decode()

    def generate_visit_key(self) -> str:
        # Generate a unique visit key based on configuration
        return ''.join(random.choices(self.h5st_algo_config['visitKey']['seed'],
                                      k=self.h5st_algo_config['visitKey']['randomLength']))

    @staticmethod
    def decode_base64_url(data: str) -> str:
        padding = '=' * (4 - len(data) % 4)
        return base64.urlsafe_b64decode(data + padding).decode()

    def get_random_id_pro(self) -> str:
        # Placeholder for generating a random ID
        return ''.join(random.choices("0123456789ABCDEF", k=32))
